use crate::iter::FractalIterator;
use crate::{LineCommand, PathCommand, PathIterator, Point, Transform, Transformation};

pub mod cubic;
pub mod line;
pub mod quadratic;

pub use cubic::CubicSegment;
pub use line::LineSegment;
pub use quadratic::QuadraticSegment;

/// An iterator over line path commands that approximate `self` with tolerance `epsilon`.
#[derive(Clone, Debug)]
pub struct Linearizer<P> {
	path: P,
	epsilon: f32,
}

impl<P> Linearizer<P> {
	pub fn new(path: P, epsilon: f32) -> Self {
		Self { path, epsilon }
	}
}

impl<P> Linearizer<P>
where
	P: PathIterator,
{
	pub fn finish(self) -> Vec<LineCommand> {
		self.collect::<Vec<_>>()
	}
}

impl<P> FractalIterator for Linearizer<P>
where
	P: PathIterator,
{
	type Item = LineCommand;

	fn for_each(self, f: &mut impl FnMut(Self::Item) -> bool) -> bool {
		let mut initial_point = None;
		let mut current_point = None;
		self.path.for_each({
			let epsilon = self.epsilon;
			&mut move |command| match command {
				PathCommand::MoveTo(p) => {
					initial_point = Some(p);
					current_point = Some(p);
					f(LineCommand::MoveTo(p))
				}
				PathCommand::LineTo(p) => {
					current_point = Some(p);
					f(LineCommand::LineTo(p))
				}
				PathCommand::QuadraticTo(p1, p) => {
					QuadraticSegment::new(current_point.unwrap(), p1, p)
						.linearize(epsilon)
						.for_each(&mut |p| {
							current_point = Some(p);
							f(LineCommand::LineTo(p))
						})
				}
				PathCommand::CubicTo(p1, p2, p) => {
					CubicSegment::new(current_point.unwrap(), p1, p2, p)
						.linearize(epsilon)
						.for_each(&mut |p| {
							current_point = Some(p);
							f(LineCommand::LineTo(p))
						})
				}
				PathCommand::Close => {
					current_point = initial_point;
					f(LineCommand::Close)
				}
			}
		})
	}
}
